Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply license choices to SPDX expressions #3425

Merged
merged 1 commit into from
Jan 13, 2021

Conversation

MarcelBochtler
Copy link
Member

@MarcelBochtler MarcelBochtler commented Dec 7, 2020

This is a draft how license choices should be applied.
The signature of the tested function looks like this:
fun applyChoice(choice: SpdxExpression, subtreeMatcher: SpdxExpression = this): SpdxExpression

See #3396 and #2610

@MarcelBochtler MarcelBochtler force-pushed the license-choice branch 6 times, most recently from 143e47f to 93b2c38 Compare December 9, 2020 14:25
@MarcelBochtler MarcelBochtler changed the title Proposal: Apply license choices Apply license choices to SPDX expressions Dec 9, 2020
@MarcelBochtler MarcelBochtler marked this pull request as ready for review December 9, 2020 14:29
@MarcelBochtler MarcelBochtler force-pushed the license-choice branch 2 times, most recently from e590394 to 7e24729 Compare December 10, 2020 08:00
* Applies a license [choice] to the given SPDX [subtreeMatcher].
* The [subtreeMatcher] must not contain more than two choices.
*/
open fun applyChoice(subtreeMatcher: SpdxExpression, choice: SpdxExpression): SpdxExpression {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the API here, why is choice not applied to this but to subtreeMatcher? And why must it not contain more than two choices?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is choice not applied to this but to subtreeMatcher?

Because the choice might only be a choice of a single subtree. E.g. (a OR b) AND c.
In my implementation the user would choose b for the subtree a OR b and the result would be a AND c.
Another approach would be to not use subtree matchers at all. E.g. if the choice is b it will be chosen no matter if the expression is a OR b, a OR b OR c or (a OR b) AND c.
I had the impression from our conversation and from #2610 that this is the wanted behavior.

And why must it not contain more than two choices?

Because I thought that a OR b OR c should be handled in two separate choices:

  • a OR b choose b
  • b OR c choose b
    Allowing three choices would end in even more combinations that need to be covered:
  • a OR b choose b
  • b OR c choose b
  • a OR b OR c choose c

If you see a use case that this separation is required I'd agree to remove this limitation.

Copy link
Member

@sschuberth sschuberth Dec 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH, I had a different understanding of how choices should work. In my view, a valid choice should always be checked against the whole expression. There should be no such thing as choices on sub-expressions.

That is, if the original expression would be (a OR b) AND c, then b would not be a valid choice, as it omits c. The only valid choices would be a AND c or b AND c.

Similarly, the only valid choices for a OR b OR c would be a, b or c. There should not be any "intermediate" choices like a OR b, and after applying a choice, the resulting expression must not contain OR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mnonnenmacher do you agree with @sschuberth?
I think this would make the API and the implementation easier and the configuration for users more straight forward.

Copy link
Member

@fviernau fviernau Dec 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH, I'm a bit surprised that making choices for sub-trees is being questioned again, as we discussed this in the meeting and I believe it became clear that it should be supported.

Picking up Sebastian's example (a OR b) AND c I believe the choice (a OR b) -> b should be applicable to it and resolve it to `b AND c´.

That is, I believe the model of a choice should be a tuple (expression, target-expression) whereas given any input expression X it would replace all subtrees of X which are equivalant to expression with target-expression.

Copy link
Member

@sschuberth sschuberth Dec 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I'm simply confused about the API, even after looking at the tests, mostly due to variable naming I believe. In particular I do not like the term "subtreeMatcher", but prefer "subExpression".

I can see a value of being able to first "narrow down" the choice, and incrementally doing that to get a final choice which does not contain an OR anymore.

What would help at least me for a better understanding would be to change the function as follows:

/**
 * Apply a license [choice], optionally limited to the given [subExpression], and return an [SpdxExpression] where
 * the choice is resolved.
 */
open fun applyChoice(choice: SpdxExpression, subExpression: SpdxExpression = this): SpdxExpression {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And why must it not contain more than two choices?

Because I thought that a OR b OR c should be handled in two separate choices:

IMO, ideally the API would work in a way that I can generically say "whenever I come across sub-expression X, choose Y", so I should be able to say "whenever I come across sub-expression a OR b OR c, choose b" or also "whenever I come across sub-expression a OR b OR c, choose b OR c".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, ideally the API would work in a way that I can generically say "whenever I come across sub-expression X, choose Y", so I should be able to say "whenever I come across sub-expression a OR b OR c, choose b" or also "whenever I come across sub-expression a OR b OR c, choose b OR c".

Fully agree, that seem 100% inline with what I envisioned as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the API to your suggestions and tried to improve the readability.
Please also have a look at the tests that cover everything that I thought of. If a test doesn't exist I forgot that specific use case.

Short summary how the API will behave:

  • Whenever I come across sub-expression a OR b OR c, choose b
  • Whenever I come across sub-expression a OR b OR c, choose b OR c
  • If the sub-expression does not match the expression, the expression is returned (should we use
  • Throws an exception if the choice is not valid for the subExpression

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fviernau, @sschuberth please have another look at the changes

spdx-utils/src/main/kotlin/SpdxExpression.kt Outdated Show resolved Hide resolved
spdx-utils/src/main/kotlin/SpdxExpression.kt Outdated Show resolved Hide resolved
spdx-utils/src/test/kotlin/SpdxExpressionTest.kt Outdated Show resolved Hide resolved
spdx-utils/src/test/kotlin/SpdxExpressionTest.kt Outdated Show resolved Hide resolved
spdx-utils/src/main/kotlin/SpdxExpression.kt Outdated Show resolved Hide resolved
spdx-utils/src/main/kotlin/SpdxExpression.kt Outdated Show resolved Hide resolved
spdx-utils/src/main/kotlin/SpdxExpression.kt Outdated Show resolved Hide resolved
spdx-utils/src/main/kotlin/SpdxExpression.kt Outdated Show resolved Hide resolved
spdx-utils/src/main/kotlin/Extensions.kt Outdated Show resolved Hide resolved
spdx-utils/src/main/kotlin/Extensions.kt Outdated Show resolved Hide resolved
spdx-utils/src/main/kotlin/Extensions.kt Outdated Show resolved Hide resolved
spdx-utils/src/main/kotlin/SpdxExpression.kt Outdated Show resolved Hide resolved
spdx-utils/src/main/kotlin/SpdxExpression.kt Outdated Show resolved Hide resolved
Copy link
Member

@sschuberth sschuberth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm basically fine with the formal aspects of the PR now, but I'd like @mnonnenmacher to approve on the API.

spdx-utils/src/main/kotlin/SpdxExpression.kt Outdated Show resolved Hide resolved
@MarcelBochtler MarcelBochtler force-pushed the license-choice branch 5 times, most recently from 773604a to c3dc5de Compare January 12, 2021 14:37
@MarcelBochtler MarcelBochtler force-pushed the license-choice branch 2 times, most recently from 5cfbac4 to 2d61fa4 Compare January 12, 2021 15:03
sschuberth
sschuberth previously approved these changes Jan 12, 2021
Copy link
Member

@sschuberth sschuberth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fviernau @mnonnenmacher I believe this is the API we agreed on and now finally ready. Please also do a re-review so we're sure you're fine with the API and we can take it into use without expecting it to change again soon. Thanks in advance!

override fun applyChoice(choice: SpdxExpression, subExpression: SpdxExpression): SpdxExpression {
if (!subExpression.validChoices().containsAll(choice.validChoices())) {
throw InvalidLicenseChoiceException(
"$choice is not a valid choice for the subtree expression. Valid choices are: ${validChoices()}."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"subexpression" instead of "subtree expression", and I think it would be good to also print the subexpression in this message. Or maybe just say "...is not a valid choice for $subExpression. ..."

return replaceMatcherWithChoice(subExpression, choice)
}

private fun replaceMatcherWithChoice(subExpression: SpdxExpression, choice: SpdxExpression): SpdxExpression {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is "matcher" a left-over from a renaming? Maybe rename the function to replaceSubexpressionWithChoice.


private fun replaceMatcherWithChoice(subExpression: SpdxExpression, choice: SpdxExpression): SpdxExpression {
val expressionString = toString()
val matcherString = subExpression.toString()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, rename to subexpressionString?

@@ -516,3 +558,5 @@ enum class SpdxOperator(
*/
OR(0)
}

class InvalidLicenseChoiceException(message: String) : Exception(message)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to extend SpdxException instead?

val expression = "a OR b OR c".toSpdx()
val choice = "a".toSpdx()

val result = expression.applyChoice(choice)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently this is the same test as "apply the choice if the expression contains multiple choices" below, according to the test name I think you wanted to pass a subexpression here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was a leftover from before the API change where not more than one choice were allowed. As now choices can be applied I removed this test.

result shouldBe "b".toSpdx()
}

"throw an exception if the chosen license is not an valid option" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: "a valid"

val choice = "x".toSpdx()
val subExpression = "x OR y OR z".toSpdx()

val result = expression.applyChoice(choice, subExpression)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it throw an exception if the subexpression is invalid like for invalid license choices?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I agree that this would make the API more consistent and will fail early if the developer uses this API in another way as it was intended.

@mnonnenmacher
Copy link
Member

@fviernau @mnonnenmacher I believe this is the API we agreed on and now finally ready. Please also do a re-review so we're sure you're fine with the API and we can take it into use without expecting it to change again soon. Thanks in advance!

I'm mostly fine with the API and had only minor comments. The only API change I mentioned in my comments is if we should throw an exception if applyChoice is called with an invalid subExpression like we do for invalid choices.

To be able to apply license choices to SpdxCompoundExpressions, a logic
is added to apply the user's choices to a specified matcher.

Signed-off-by: Marcel Bochtler <marcel.bochtler@bosch.io>
Copy link
Member

@sschuberth sschuberth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also need @mnonnenmacher to re-review and approve.

@sschuberth
Copy link
Member

Merging this despite the stuck markdown-link-check as all other checks passed.

@sschuberth sschuberth merged commit b65017c into oss-review-toolkit:master Jan 13, 2021
@sschuberth sschuberth deleted the license-choice branch January 13, 2021 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants